#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <pthread.h>

#include <unistd.h>
#include <string.h>

#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>

#define WEBROOT "webroot"
#define SERVER_PORT 8080

// 自定义结构体类型： 客户端信息
typedef struct {
   int fd;  // 套接字
   char ip[INET_ADDRSTRLEN]; // 客户端的IP地址
   uint16_t port; // 客户端的端口号
}Client;

char OK[] = {
   "HTTP/1.1 200 OK\r\n"
   "Server: C/WebServer1.0\r\n"
   "Content-Type: text/html;charset=utf-8\r\n"
   "Connection: close\r\n"
   "\r\n"
   "<h3>OK</h3>"
};

char bytes_header[]={
    "HTTP/1.1 200 OK\r\n"
    "accept-ranges: bytes\r\n"
    "content-length: %ld\r\n"
    "content-type: %s\r\n"
    "Connection: keep-alive\r\n"
    "\r\n"
};

char P_404[] = {
   "HTTP/1.1 404 Not Found\r\n"
   "Server: C/WebServer1.0\r\n"
   "Content-Type: text/html;charset=utf-8\r\n"
   "Connection: keep-alive\r\n"
   "\r\n"
   "<h3>无效资源</h3>"
};

// 响应头部信息
int response_header(int sockfd, char *path, char *filepath){  // /index.html,  /login.html
    // char filepath[256] = ""; // 访问资源的文件路径
    if(strcmp("/", path) == 0){  // 站点的根路径，默认转到index.html
        sprintf(filepath, "%s/index.html", WEBROOT);
    }else{
        sprintf(filepath, "%s%s", WEBROOT, path);
    }

    // 获取文件大小
    struct stat s;
    if(stat(filepath, &s) != 0){
        send(sockfd, P_404, strlen(P_404), 0);
        return 0;  // 只响应头，不响应数据
    }

    // 获取文件的类型（响应数据的类型-媒体类型， /etc/mime.types）
    char *content_type=NULL;
    if(strstr(path, ".html") || strstr(path, ".htm")){
        content_type = "text/html;charset=utf-8";
    }else if(strstr(path, ".png") || strstr(path, ".jpg") || strstr(path, ".ico")){
        content_type = "image/*";
    }else if(strstr(path, ".css")){
        content_type = "text/css;charset=utf-8";
    }else if(strstr(path, ".js")){
        content_type = "application/javascript;charset=utf-8";
    }else if(strstr(path, ".mp3")){
        content_type = "audio/mpeg";
    }

    char response[512] = "";
    sprintf(response, bytes_header, s.st_size, content_type);
    send(sockfd, response, strlen(response), 0);
    return 1;
}

void *client_task(void *arg){
    Client *client = (Client *)arg;
    printf("%s connected!\n", client->ip);

    while(1){  // 长连接

    // 开始接收请求报文数据, 获取出第一行的请求方法、请求路径
    char request[2048] = "";
    char path[512] = "";// 请求路径
    char method[16] = ""; // 请求方法
    while(1){
        ssize_t len = recv(client->fd, request, 8192, 0);
        if(len == 0) break; // 浏览器关闭了
        // printf("%s", request);
        if(strlen(path) == 0){
            // 第一次接收到请求报文
            char *line1 = strtok(request, "\r\n");
            char *method_p = strtok(line1, " ");
            char *path_p = strtok(NULL, " ");
            if(method_p != NULL){
                strncpy(method, method_p, strlen(method_p));
            }
            if(path_p != NULL){
                strncpy(path, path_p, strlen(path_p));
            }
        }

        if(len < 2048){
            break;
        }
    }

   
    printf("%s %s %s 200 OK\n", client->ip, method, path);
    // 响应数据: 依据请求资源路径来响应数据
    // 检查当前站点是否有请求资源： 有（读文件数据并回应），没有（回应404）
    char filepath[256]="";
    if(response_header(client->fd, path, filepath)){
        // 响应数据
        int fd = open(filepath, O_RDONLY);
        char data[2048]="";
        while(1){
            int datalen = read(fd, data, 2048);
            send(client->fd, data, datalen, 0);
            if(datalen < 2048) break;
        }
    }

    // 长连接内
    }
    // 由于浏览器主动关闭
    close(client->fd);
    
    return NULL;
}

int sockfd;
void exit_handler(int signum){
    close(sockfd);
    printf("\n");
    _exit(0);
}

int main(int argc, char const *argv[])
{
    signal(SIGINT, exit_handler);

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    int opt=1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &opt, 4);  // 端口重新使用

    // 读取配置文件
    char *port = NULL;
    int conf = open("server.cnf", O_RDONLY);
    if(conf > 0){
       char conf_buf[2048] = "";
       read(conf, conf_buf, 2048);
       char  *line;
       line = strtok(conf_buf, "\r\n");
       while(line){
            if(strstr(line, "port")){
                strtok(line, "=");
                port = strtok(NULL, "=");
                break;
            }
            line = strtok(NULL, "\r\n");
       }
       close(conf);
    }

    struct sockaddr_in bind_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(port?atoi(port):SERVER_PORT),
        .sin_addr.s_addr = INADDR_ANY
    };

     // 绑定本地网卡
    if(bind(sockfd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) != 0){
        printf("绑定失败: %s\n", strerror(errno));
        return -1;
    }

    listen(sockfd, 1024);  // 设置监听最大连接数
    printf("---WebServer(%d) running(Ctrl+C退出)---\n", port?atoi(port):SERVER_PORT);
    while(1){
        // 等待客户端连接
        struct  sockaddr_in cli_addr;
        socklen_t cli_addr_len = sizeof(cli_addr);

        Client *client_p = (Client *)calloc(1, sizeof(Client));

        client_p->fd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_addr_len);
       
        // 有客户连接： 收集客户端信息（传入到客户端线程任务函数中）
        inet_ntop(AF_INET, &cli_addr.sin_addr.s_addr, client_p->ip, 16); // 将客户端的地址IP值转化字符串
        client_p->port = ntohs(cli_addr.sin_port); // 获取客户端的端口号

        pthread_t tid;
        pthread_create(&tid, NULL, client_task, client_p);  // 创建客户端通信的线程
        sleep(1);
    }


    return 0;
}
